/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tdom-internal.h"
#include "tdom.h"

enum tdom_parser_status {
    PARSER_INITIALIZED = 0,
    PARSER_PROCESSING,
    PARSER_DONE,
    PARSER_ERROR,
};

struct tdom_parser {
    XML_Parser expat;
    struct tdom_doc *doc;
    struct tdom_elem *current;
    enum tdom_parser_status status;
    struct tdom_text *last_text;
};

static void start_handler(void *data, const XML_Char *name, const XML_Char **atts)
{
    int i;
    const char *prefix;
    struct tdom_elem *elem;
    struct tdom_parser *parser = (struct tdom_parser *)data;

    if (cut_tail_space(parser->last_text) < 0) {
        parser->status = PARSER_ERROR;
        return;
    }
    parser->last_text = NULL;

    elem = tdom_elem_new(parser->current, name);
    if (elem == NULL) {
        parser->status = PARSER_ERROR;
        return;
    }

    for (i = 0; atts[i]; i += 2) {
        if (strncmp("xmlns", atts[i], 5) != 0) {
            if (tdom_set_attr(elem, atts[i], atts[i + 1], NULL) < 0) {
                goto out;
            }
            continue;
        }

        if ((atts[i][5] == ':') && (atts[i][6] != '\0')) {
            prefix = &atts[i][6];
        } else if (atts[i][5] == '\0') {
            prefix = "";
        } else {
            if (tdom_set_attr(elem, atts[i], atts[i + 1], NULL) < 0) {
                goto out;
            }
            continue;
        }

        if (tdom_set_ns(elem, prefix, atts[i + 1]) < 0) {
            goto out;
        }
    }

    if (parser->current) {
        tdom_append_child(parser->current, &elem->node);
    } else {
        parser->doc->root = elem;
    }
    parser->current = elem;

    return;

out:
    parser->status = PARSER_ERROR;
    tdom_elem_delete(elem);
}

static void end_handler(void *data, const XML_Char *name)
{
    struct tdom_parser *p = (struct tdom_parser *)data;

    if (cut_tail_space(p->last_text) < 0) {
        p->status = PARSER_ERROR;
        return;
    }
    p->last_text = NULL;

    if ((p->current = p->current->node.parent) == NULL) {
        p->status = PARSER_DONE;
    }
}

static void char_handler(void *data, const XML_Char *s, int len)
{
    struct tdom_text *text = NULL;
    char *value;
    struct tdom_parser *parser = (struct tdom_parser *)data;

    if (parser->last_text) {
        size_t size = strlen(parser->last_text->value) + len + 1;
        if ((value = malloc(size)) == NULL) {
            goto out;
        }
        snprintf(value, size, "%s%s", parser->last_text->value, s);
        value[size - 1] = '\0';
        free(parser->last_text->value);
        parser->last_text->value = value;
    } else {
        if ((s = skip_head_space(s, &len)) == NULL) {
            return;
        }
        tdom_assert(len > 0);
        if ((text = tdom_text_new(parser->current, NULL)) == NULL) {
            goto out;
        }
        if ((text->value = malloc(len + 1)) == NULL) {
            goto out;
        }
        snprintf(text->value, len + 1, "%s", s);
        text->value[len] = '\0';
        TAILQ_INSERT_TAIL(&parser->current->children, &text->node, sibling);
        parser->last_text = text;
    }

    return;
out:
    parser->status = PARSER_ERROR;
    tdom_text_delete(text);
}

static void decl_handler(void *data,
                         const char *version,
                         const char *encoding,
                         int standalone)
{
    struct tdom_parser *parser = (struct tdom_parser *)data;

    tdom_assert(parser->status == PARSER_PROCESSING);
    tdom_assert(parser->doc);

    if (tdom_doc_set(parser->doc, version, encoding) < 0) {
        parser->status = PARSER_ERROR;
    }
}

struct tdom_parser *tdom_parser_new()
{
    struct tdom_parser *parser = malloc(sizeof(struct tdom_parser));
    if (parser == NULL) {
        return NULL;
    }
    memset(parser, 0, sizeof(struct tdom_parser));
    parser->status = PARSER_INITIALIZED;

    parser->doc = tdom_doc_new(NULL, NULL, NULL);
    if (parser->doc == NULL) {
        goto out;
    }

    parser->expat = XML_ParserCreate(NULL);
    if (parser->expat == NULL) {
        goto out;
    }

    XML_SetUserData(parser->expat, parser);
    XML_SetXmlDeclHandler(parser->expat, decl_handler);
    XML_SetElementHandler(parser->expat, start_handler, end_handler);
    XML_SetCharacterDataHandler(parser->expat, char_handler);

    return parser;

out:
    tdom_parser_delete(parser);
    return NULL;
}

void tdom_parser_delete(struct tdom_parser *parser)
{
    if (parser == NULL) {
        return;
    }
    XML_ParserFree(parser->expat);
    tdom_doc_delete(parser->doc);
    free(parser);
}

int tdom_parser_parse(struct tdom_parser *parser, const char *s, int len)
{
    enum XML_Error error;

    tdom_assert(parser);
    tdom_assert(s);
    tdom_assert(len >= 0);

    if (parser->status == PARSER_INITIALIZED) {
        parser->status = PARSER_PROCESSING;
    } else if (parser->status != PARSER_PROCESSING) {
        return -1;
    }

    if (XML_Parse(parser->expat, s, len, 0) == XML_STATUS_OK) {
        if (parser->status == PARSER_PROCESSING) {
            return 0;
        } else if (parser->status == PARSER_DONE) {
            if (XML_Parse(parser->expat, NULL, 0, 1) == XML_STATUS_OK) {
                return 1;
            } else {
                return -3;
            }
        } else {
            return -2;
        }
    }

    error = XML_GetErrorCode(parser->expat);
    if (error == XML_ERROR_NO_MEMORY) {
        return -2;
    }
    return -3;
}

struct tdom_doc *tdom_parser_get_doc(struct tdom_parser *parser)
{
    struct tdom_doc *doc = NULL;

    tdom_assert(parser);

    if (parser->status == PARSER_DONE) {
        doc = parser->doc;
        parser->doc = NULL;
    }

    return doc;
}

